/*
 * Copyright (c) 2008, Xiong Yingfei, the University of Tokyo All rights
 * reserved.
 */
package ac.jp.u_tokyo.SyncLib.dictionaries;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import ac.jp.u_tokyo.SyncLib.CompilerParameters;
import ac.jp.u_tokyo.SyncLib.Mod;
import ac.jp.u_tokyo.SyncLib.NullMod;
import ac.jp.u_tokyo.SyncLib.PrimMod;
import ac.jp.u_tokyo.SyncLib.SynchronizationFailedException;
import ac.jp.u_tokyo.SyncLib.Value2Mod;
import ac.jp.u_tokyo.SyncLib.util.Helper;

public class FixedDictDynamicGet<K> extends DynamicGetBase
{
	boolean _keyOverValue;

	KeyFactory<K> _keyFactory = null;

	public FixedDictDynamicGet(boolean keyOverValue, KeyFactory<K> keyFactory)
	{
		_keyOverValue = keyOverValue;
		_keyFactory = keyFactory;
	}

	@Override
	protected Mod[] internalSync(Mod[] modifications, Object oldKey,
			Map nullableDict, Object value)
			throws SynchronizationFailedException
	{
		
		if (false == modifications[1] instanceof NullMod
				&& false == modifications[1] instanceof DictMod)
			throw new SynchronizationFailedException();

		if (false == modifications[0] instanceof PrimMod
				&& false == modifications[0] instanceof NullMod)
		{
			throw new SynchronizationFailedException();
		}

		Object newKey = modifications[0].apply(oldKey);
		Map<K, Object> dict = nullableDict;
		if (dict == null) dict = new HashMap<K, Object>();

		if (false == modifications[0] instanceof NullMod)
		{
			return modifyValue(modifications, value, newKey, dict);

		}
		else
		{
			if (_keyOverValue)
			{
				try
				{
					return modifyValue(modifications, value, newKey, dict);
				}
				catch (SynchronizationFailedException e)
				{

				}
			}
			//modify key
			Object newValue = modifications[2].apply(value);
			if (newValue == null) throw new SynchronizationFailedException();
			
			if (newKey != null)
				if (isEqualOnKey(newKey, dict, newValue, modifications))
				{
					return new Mod[] { modifications[2] instanceof NullMod ? modifications[0] : new PrimMod(newKey),
							modifications[1], modifications[2] };
				}

			Set<K> allKeys = new HashSet<K>();
			if (modifications[1] instanceof DictMod)
				allKeys.addAll(((DictMod) modifications[1]).getKeys());
			allKeys.addAll(dict.keySet());
			for (Object key : allKeys)
			{
				if (isEqualOnKey(key, dict, newValue, modifications))
				{
					return new Mod[] { new PrimMod<Object>(key),
							modifications[1], modifications[2] };
				}
			}

			if (_keyFactory != null)
			{
				Object createdKey;
				if (CompilerParameters.UseGlobalKeyFactory)
					createdKey = _keyFactory.createNewKey(null);
				else
					createdKey = _keyFactory.createNewKey(allKeys);
				Mod newDictMod = DictModFactory.creatvPut(createdKey,
						Value2Mod.findMod(null, newValue)).merge(
						modifications[1]);
				return new Mod[] { new PrimMod<Object>(createdKey), newDictMod,
						modifications[2] };
			}
			throw new SynchronizationFailedException();

		}

	}

	private Mod[] modifyValue(Mod[] modifications, Object value, Object newKey,
			Map<K, Object> dict) throws SynchronizationFailedException
	{
		if (newKey == null) throw new SynchronizationFailedException();
		Object newdv = getNewInnerValue(modifications, newKey, dict);
		if (Helper.areEqual(newdv, value) && modifications[0] instanceof NullMod) return modifications;
		Mod vmod;
		if (newdv == null) 
			vmod = PrimMod.NULL;
		else
			vmod = Value2Mod.findMod(null, newdv);
		if (!vmod.merge(modifications[2]).equals(vmod))
			throw new SynchronizationFailedException();
		else
			return new Mod[] { new PrimMod(newKey), modifications[1],
					vmod };
	}

	private boolean isEqualOnKey(Object newKey, Map<K, Object> dict,
			Object newValue, Mod[] modifications)
	{
		return Helper.areEqual(getNewInnerValue(modifications, newKey, dict),
				newValue);
	}

	private Object getNewInnerValue(Mod[] modifications, Object key,
			Map<K, Object> dict)
	{
		Mod innerMod;
		if (modifications[1] instanceof NullMod)
		{
			innerMod = NullMod.INSTANCE;
		}
		else
		{
			innerMod = ((DictMod) modifications[1]).getMod(key);
		}
		Object dv = dict.get(key);
		Object newdv = innerMod.apply(dv);
		return newdv;
	}

	@Override
	public String toString()
	{
		return "FixedDictDynamicGet@" + this.hashCode();
	}

}
